#ifndef NEURALQUANTIZER_H
#define NEURALQUANTIZER_H

#include "quantizer.h"

namespace WingGif {

class NeuralQuantizer : public Quantizer {
public:
  NeuralQuantizer(int samplingFactor, int maximumColors = 256);
  void firstPass(QByteArray pixels) override;
  QVector<QColor> buildPalette() override;
  uchar quantizePixel(QColor pixel) override;

private:
  void learn(QByteArray pixels);

  void setNeighbourhoodAlphas(QVector<int> neighbourhoodAlphas,
                              int neighbourhoodSize, int alpha, int radiusBias);
  // Sets the alpha values for moving neighbouring neurons.

  int getPixelIndexIncrement(int pictureByteCount);
  // Calculates an increment to step through the pixels of the image, such that
  // all pixels will eventually be examined, but not sequentially. This is
  // required because the learning loop needs to examine the pixels in a
  // pseudo-random order.
  // return : The increment.

  int findClosestAndReturnBestNeuron(int blue, int green, int red);
  // Finds the neuron which is closest to the supplied color, increases its
  // frequency and decreases its bias (Search for biased BGR values). Finds the
  // best neuron (close to the supplied color but not already chosen too many
  // times) and returns its index in the neural network.
  // return : The index in the neural network of a neuron which is close to the
  // supplied coordinate but which hasn't already been chosen too many times.

  void moveNeuron(int alpha, int neuronIndexToMove, int blue, int green,
                  int red);
  // Moves the neuron at the supplied index in the neural network closer to the
  // supplied color by a factor of alpha.
  // Move neuron i towards biased (b,g,r) by factor alpha.

  void moveNeighbouringNeurons(int neighbourhoodSize, int neuronIndex, int blue,
                               int green, int red);

  // Moves neighbours of the neuron at the supplied index in the network closer
  // to the supplied colour. Move adjacent neurons by precomputed alpha *
  // (1-((i-j)^2/[r]^2)) in radpower[|i-j|]

  void moveNeighbour(int neuronIndexToMove, int alpha, int alphaRadiusBias,
                     int blue, int green, int red);
  // Moves an individual neighbouring neuron closer to the supplied colour by a
  // factor of alpha.

  void unbiasNetwork();
  // Unbias network to give byte values 0..255 and record position i to prepare
  // for sort.

  void buildIndex();
  // Insertion sort of network and building of netindex[0..255] (to do after
  // unbias). Populates the _indexOfGreen array with the indices in the network
  // of colors with green values closest to 0 to 255.

  int indexOfLeastGreenNeuron(int startNeuronIndex);
  // Gets the index in the network of the neuron with the lowest green value,
  // between the supplied index and the end of the network.
  // startNeuronIndex : The index in the network to start searching at.
  // return : The index of the least green neuron.

  uchar mapColor(int blue, int green, int red);
  // Gets the index in the color table of the color closest to the supplied
  // color.
  // return : Index in the colour table

private:
  int _networkSize;
  // Number of neurons in the neural network. Also the maximum number of colors
  // in the quantized frame. Each neuron represents one of the colors in the
  // palette of the quantized image.

  int _maximumNeuronIndex;
  // Maximum possible neuron index within the neural network.
  // One less than the number of neurons in the network.

  const int networkBiasShift = 4;
  // Controls the relationship between values supplied in the learning data and
  // the values of the neurons in the network during learning.
  //  The larger this value, the larger the values held in the neurons will be
  //  in comparison to the values supplied during the learning loop. This allows
  //  a more precise positioning of the neurons whilst still using integer
  //  arithmetic.

  const int numberOfLearningCycles = 100;
  // Number of learning cycles. The greater this value, the more often the
  // alpha values used to move neurons will be decremented.

  const int prime1 = 499;
  // First prime number near 500.
  // Assumes no image has a length so large that it is divisible all four
  // primes.

  const int prime2 = 491;
  // Second prime number near 500.
  // Assumes no image has a length so large that it is divisible all four
  // primes.

  const int prime3 = 487;
  // Third prime number near 500.
  // Assumes no image has a length so large that it is divisible all four
  // primes.

  const int prime4 = 503;
  // Fourth prime number near 500.
  // Assumes no image has a length so large that it is divisible all four
  // primes.

  QVector<int> _biases;
  // Array of values which decrease as the frequency of the corresponding neuron
  // increases.
  // The index into this array is the same as the index into the array of
  // neurons.

  QVector<int> _frequencies;
  // Array of values which indicate how often each neuron in the network has
  // been chosen during the learning process. The index to this array is the
  // same as the index in the array of neurons.

  QVector<int> _neighbourhoodAlphas;
  // Alpha values controlling how far towards a target coordinate any
  // neighbouring neurons are moved.

  const int alphaBiasShift = 10; /* alpha starts at 1.0 */
  // The initial value of alpha will be set to 1, left shifted by this many
  // bits.

  const int initialAlpha = 1 << alphaBiasShift;
  // The starting value of alpha.
  // Alpha is a factor which controls how far neurons are moved during the
  // learning loop, and it decreases as learning proceeds.

  const int intBiasShift = 16;
  // Bias for fractions. The larger this value is, the larger IntBias will be.
  // Larger values will also make the bias of a neuron a more significant factor
  // than the distance from the supplied coordinate when identifying the best
  // neuron for a given coordinate.

  const int intBias = 1 << intBiasShift;
  // The larger this value is, the higher the initial frequency will be for each
  // neuron, and the more the bias and frequency of the closest neuron will be
  // adjusted by during the learning loop.

  const int gammaShift = 10; /* Gamma = 1024 */
  // The larger this value is, the larger Gamma will be.
  // Larger values also result in the bias of all neurons being increased by a
  // greater amount in each iteration through the learning process.

  const int betaShift = 10;
  // The larger this value is, the smaller ClosestNeuronFrequencyIncrement and
  // ClosestNeuronBiasDecrement will be. This means that larger values will also
  // result in the frequency of all neurons being decreased by less and the bias
  // being increased by less at each step of the learning loop.

  const int closestNeuronFrequencyIncrement =
      intBias >> betaShift; /* Beta = 1/1024 */
  // The larger this value is, the more the frequency of the closest neuron will
  // be increased by during the learning loop.

  const int closestNeuronBiasDecrement = intBias << (gammaShift - betaShift);
  // The larger this value is, the more the bias of the closest neuron will be
  // decreased by during the learning loop.

  int _initialNeighbourhoodSize;
  // Initial radius.
  // The initial unbiased neuron neighbourhood size is set to this multiplied by
  // the neighbourhood size bias. This is also the size of the array of alphas
  // for shifting neighbouring neurons.

  const int neighbourhoodSizeBiasShift = 6;
  // The neuron neighbourhood size is set by shifting the unbiased neighbourhood
  // size this many bits to the right.

  const int neighbourhoodSizeBias = 1 << neighbourhoodSizeBiasShift;
  // Radius bias.
  // The initial unbiased neuron neighbourhood size is set to this multiplied by
  // the initial radius.

  int _initialUnbiasedNeighbourhoodSize;
  // The initial value for the unbiased size of a neuron neighbourhood.

  const int unbiasedNeighbourhoodSizeDecrement = 30;
  // Factor for reducing the unbiased neighbourhood size.

  const int radiusBiasShift = 8;
  // The greater this value, the greater RadBias and AlphaRadBiasShift will be.

  const int radiusBias = 1 << radiusBiasShift;
  // The greater this value, the larger alpha will be, and the more neighbouring
  // neurons will be moved by during the learning process.

  const int alphaRadiusBiasShift = (alphaBiasShift + radiusBiasShift);
  // The greater this value, the greater _alphaRadBias will be, and so the less
  // neighbouring neurons will be moved by during the learning process.

  const int alphaRadiusBias = 1 << alphaRadiusBiasShift;
  // The greater this value, the less neighbouring neurons will be moved by
  // during the learning process.

  int _pixelBytesCount;
  // Height * Width *3 (H*W*3).

  int _samplingFactor;
  // Gets and sets quality of color quantization (conversion of images to the
  // maximum 256 colors allowed by the GIF specification). Lower values (minimum
  // = 1) produce better colors, but slow processing significantly. 10 is the
  // default, and produces good color mapping at reasonable speeds. Values
  // greater than 20 do not yield significant improvements in speed.

  QVector<QVector<int>> _network; /* the network itself - [netsize][4] */
  // The neural network.
  // An array of 256 neurons, each of which is an array of 4 bytes.
  // Each neuron holds a colour intensity, in the order red, green, blue.
  // The fourth element of the array holds the neuron's original index in the
  // network before it is sorted.

  QVector<int> _indexOfGreen;
  // Used for locating colours in the neural network - the index of this array
  // is the green value of the colour to look for.
};

} // namespace WingGif

#endif // NEURALQUANTIZER_H
